﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using Microsoft.Win32;
using System.IO;

namespace DreamShield.Deployment.Miles.ApplicationUtils.ContextSwitch
{
    /// <summary>
    /// Le runtime DreamShield est assez sensible aux dlls présentent dans le même
    /// domaine d'application. Pour avoir l'architecture la plus sure possible,
    /// toutes les opérations qui utilisent directement le runtime DreamShield sont
    /// effectuées dans un autre domaine d'application
    /// </summary>
    public sealed class DreamShieldRuntimeConnect
    {
        #region Objet connexion

        static DreamShieldRuntimeConnect connection = null;

        static object connection_creation_sync = new object();

        /// <summary>
        /// Connexion au runtime DreamShield pour le domaine d'application actuel
        /// </summary>
        public static DreamShieldRuntimeConnect Singleton
        {
            get
            {
                lock (connection_creation_sync)
                {
                    if (connection == null)
                        connection = new DreamShieldRuntimeConnect();
                    return connection;
                }
            }
        }

        #endregion

        #region Runtime management

        #region DreamShield Runtime Loading

        static class AssemblyLocator
        {
            static Dictionary<string, Assembly> assemblies;
            static Dictionary<string, Assembly> softassemblies;

            public static void Init()
            {
                assemblies = new Dictionary<string, Assembly>();
                softassemblies = new Dictionary<string, Assembly>();

                AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(CurrentDomain_AssemblyLoad);
                AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

            }

            static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
            {
                Assembly assembly = null;
                assemblies.TryGetValue(args.Name, out assembly);
                if (assembly == null)
                {
                    try
                    {
                        AssemblyName name = new AssemblyName(args.Name);
                        softassemblies.TryGetValue(name.Name.ToLower(), out assembly);
                    }
                    catch { }
                }

                return assembly;
            }

            static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
            {
                Assembly assembly = args.LoadedAssembly;

                assemblies[assembly.FullName] = assembly;
                softassemblies[assembly.GetName().Name.ToLower()] = assembly;
            }
        }

        static bool LoadDreamShieldRuntime(string asm_driver_provider, ref Version version)
        {
            RegistryKey key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\LKSoft\\Configuration\\Runtimes\\", false);
            if (key == null)
            {
                RuntimeLoadError(version);
                return false;
            }

            try
            {
                Version c_ver;
                Version last_ver = null;

                foreach (string child in key.GetSubKeyNames())
                {
                    try
                    {
                        c_ver = new Version(child);
                        if (c_ver == version)
                        {
                            last_ver = c_ver;
                            break;
                        }
                        else if (last_ver == null || c_ver > last_ver)
                        {
                            last_ver = c_ver;
                        }
                    }
                    catch { }
                }

                if (last_ver == null || last_ver < version)
                {
                    RuntimeLoadError(version);
                    return false;
                }

                version = last_ver;
                RegistryKey ver_key = key.OpenSubKey(last_ver.ToString());
                if (ver_key == null)
                {
                    RuntimeLoadError(version);
                    return false;
                }

                try
                {
                    try
                    {
                        string file_name = (string)ver_key.GetValue("dir") + Path.DirectorySeparatorChar +
                            asm_driver_provider;

                        Assembly asm = Assembly.LoadFile(file_name);
                        Type staticDrv = asm.GetType("DreamShield.Deployment.InstallationDrivers.StaticDriver", true);
                        MethodInfo drvLoad = staticDrv.GetMethod("LoadDriver", new Type[] { typeof(Version), typeof(bool) });

                        drvLoad.Invoke(null, new object[] { version, false });
                        /* false => not allowSuperior, car on s'est déjà tué à chercher la bonne version */

                        AssemblyLocator.Init();

                        return true;
                    }
                    catch (Exception ex)
                    {
                        throw new InvalidOperationException(String.Format("Une erreur est survenue :\n{0}", ex));
                    }
                }
                finally
                {
                    ver_key.Close();
                }
            }
            finally
            {
                key.Close();
            }
        }

        static void RuntimeLoadError(Version version)
        {
            throw new InvalidOperationException(String.Format("Runtime introuvable pour démarrer DreamShield de version {0} ou supérieure",
                    version.ToString()));
        }

        #endregion

        AppDomain RuntimeDomain { get; set; }

        class DreamShieldRuntimeLoader : MarshalByRefObject
        {
            public DreamShieldRuntimeLoader()
            {
            }

            public virtual Version LoadRuntime()
            {
                /* version minimale de DreamShield Runtime : celle publiée avec la version 0.97 de DreamShield*/
                Version ver = new Version(1, 15, 0, 0);
                LoadDreamShieldRuntime("DreamShield.Deployment.InstallationDrivers.dll", ref ver);

                return ver;
            }
        }

        #endregion

        #region Constructeur

        /// <summary>
        /// Obtient la version actuelle du runtime DreamShield chargée dans le domaine d'application
        /// </summary>
        public Version RuntimeVersion { get; private set; }

        DreamShieldRuntimeConnect()
        {
            InitializeDomain();
        }

        private void InitializeDomain()
        {
            AppDomainSetup setup = new AppDomainSetup();
            setup.DisallowApplicationBaseProbing = true;
            setup.ApplicationName = "DreamShield Miles ApplicationUtils - MiniApp";

            RuntimeDomain = AppDomain.CreateDomain("DreamShield Miles ApplicationUtils - Domain", null, setup);
            
            var RuntimeLoader = (DreamShieldRuntimeLoader)RuntimeDomain.CreateInstanceFromAndUnwrap(
                Assembly.GetExecutingAssembly().Location, typeof(DreamShieldRuntimeLoader).FullName);

            RuntimeVersion = RuntimeLoader.LoadRuntime();

            RuntimeEndPoint = (DreamShieldRuntimeEndPoint)RuntimeDomain.CreateInstanceFromAndUnwrap(
                Assembly.GetExecutingAssembly().Location, typeof(DreamShieldRuntimeEndPoint).FullName);
        }

        #endregion

        #region End Point

        /// <summary>
        /// Point de contact de la connexion au runtime avec ce dernier
        /// </summary>
        public DreamShieldRuntimeEndPoint RuntimeEndPoint { get; private set; }

        #endregion
    }
}
